#include <device/console.h>
#include <yonex/types.h>
#include <sys/io.h>
#include <device/clock.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

#define CRT_ADDR_REG    0x3d4
#define CRT_DATA_REG    0x3d5

#define CRT_STADDR_H    0x0c
#define CRT_STADDR_L    0x0d
#define CRT_CURSOR_H    0x0e
#define CRT_CURSOR_L    0x0f

#define ASCII_NUL       0x0
#define ASCII_ENQ       0x5
#define ASCII_BEL       0x7     // '\a'
#define ASCII_BS        0x8     // '\b'
#define ASCII_HT        0x9     // '\t'
#define ASCII_LF        0xA     // '\n'
#define ASCII_VT        0xB     // '\v'
#define ASCII_FF        0xC     // '\f'
#define ASCII_CR        0xD     // '\r'
#define ASCII_ESC       033     // ANSI CSI 控制序列

#define MEM_BASE    0xB8000
#define MEM_SIZE    0x7F80
#define MEM_END     (MEM_BASE + MEM_SIZE)
#define WIDTH       80
#define HEIGHT      25
#define ROW_SIZE    (WIDTH * 2)
#define SCR_SIZE    (ROW_SIZE * HEIGHT)

static u64 screen;  // 当前显示器开始位置
static u64 pos;     // 当前光标的内存位置
static int x, y;    // 当前光标的坐标

static u8  style = 0x07;    // 字符样式 白色
static u16 space = 0x0720;  // 带样式的空格
static u64 erase = 0x0720072007200720LL;  // 擦除

static const char *ANSI[] = {
    "[1;30m", "[1;31m",
    "[1;32m", "[1;33m",
    "[1;34m", "[1;35m",
    "[1;36m", "[1;37m"};

static void get_screen_start()
{
    outb(CRT_ADDR_REG, CRT_STADDR_H);
    screen = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG, CRT_STADDR_L);
    screen |= inb(CRT_DATA_REG);
    screen <<= 1;
    screen += MEM_BASE;
}

static void set_screen_start()
{
    outb(CRT_ADDR_REG, CRT_STADDR_H);
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 9) & 0xff);
    outb(CRT_ADDR_REG, CRT_STADDR_L);
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 1) & 0xff);
}

static void get_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    pos = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    pos |= inb(CRT_DATA_REG);
    pos <<= 1;
    pos += MEM_BASE;
    get_screen_start();
    u32 delta = (pos - screen) >> 1;
    x = delta % WIDTH;
    y = delta / WIDTH;
}

static void set_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 9) & 0xff);
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 1) & 0xff);
}

static void scroll_up()
{
    if (screen == (MEM_END - SCR_SIZE - ROW_SIZE)) {
        memcpy((uintptr_t *)MEM_BASE, (uintptr_t *)(MEM_END - SCR_SIZE), SCR_SIZE);
        screen = MEM_BASE;
        pos = MEM_BASE + SCR_SIZE - ROW_SIZE;
    }
    else {
        screen += ROW_SIZE;
        pos += ROW_SIZE;
    }
    u64 *ptr = (uintptr_t *)pos;    // 清空1行
    *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    set_screen_start();
}

static void cmd_bs()
{
    if (x) {
        x--;
        pos -= 2;
        *(short *)pos = space;
    }
}

static void cmd_ht()
{
    switch (x&7) {
        case 0: x++; *(short *)pos = space; pos+=2;
        case 1: x++; *(short *)pos = space; pos+=2;
        case 2: x++; *(short *)pos = space; pos+=2;
        case 3: x++; *(short *)pos = space; pos+=2;
        case 4: x++; *(short *)pos = space; pos+=2;
        case 5: x++; *(short *)pos = space; pos+=2;
        case 6: x++; *(short *)pos = space; pos+=2;
        case 7: x++; *(short *)pos = space; pos+=2;
    }
}

static void cmd_lf()
{
    if (y + 1 < HEIGHT) {
        y++;
        pos += ROW_SIZE;
        return;
    }
    scroll_up();
}

static void cmd_cr()
{
    pos -= (x << 1);
    x = 0;
}

static int cmd_csi(const char *buf)
{
    // 防止溢出
    if (strlen(buf) < 6)
        return 0;
    for (int i = 0; i < 8; i++) {
        if (!strncmp(ANSI[i], buf, 6)) {
            switch (i) {
                case 0 : style = 0x00; break; // BLACK
                case 1 : style = 0x04; break; // RED
                case 2 : style = 0x02; break; // GREEN
                case 3 : style = 0x06; break; // YELLOW
                case 4 : style = 0x01; break; // BLUE
                case 5 : style = 0x05; break; // MAGENTA 洋红
                case 6 : style = 0x03; break; // CYAN 青色
                case 7 : style = 0x07; break; // WHITE
            }
            return 6;
        }
    }
    return 0;
}

void console_write(const char *buf, u32 count)
{
    char ch;
    int len;
    while (count--) {
        ch = *buf++;
        switch (ch) {
            case ASCII_NUL:
                // TODO
                break;
            case ASCII_ENQ:
                // TODO
                break;
            case ASCII_BEL:
                start_beep();
                break;
            case ASCII_BS:
                cmd_bs();
                break;
            case ASCII_HT:
                cmd_ht();
                break;
            case ASCII_LF:
                cmd_cr();
                cmd_lf();
                break;
            case ASCII_VT:
                cmd_lf();
                break;
            case ASCII_FF:
                cmd_lf();
                break;
            case ASCII_CR:
                cmd_cr();
                break;
            case ASCII_ESC:
                len = cmd_csi(buf);
                buf += len;
                count -= len;
                break;
            default:
                *(char *)pos++ = ch;
                *(char *)pos++ = style;
                x++;
                if (x == WIDTH) {
                    cmd_cr();
                    cmd_lf();
                }
        }
    }
    set_cursor();
}

void console_clear()
{
    screen = MEM_BASE;
    pos = MEM_BASE;
    set_screen_start();
    set_cursor();
    get_cursor();

    u64 *ptr = (uintptr_t *)MEM_BASE;
    while (ptr < (uintptr_t *)MEM_END) {
        *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
        *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
        *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
        *ptr++ = erase; *ptr++ = erase; *ptr++ = erase; *ptr++ = erase;
    }
}

void console_init()
{
    console_clear();
}
